1. Рекурсивная агрегация экземпляров класса.
* Ответ: Это ситуация, когда объект содержит в качестве атрибутов ссылки на другие объекты того же самого класса.
* Смысл: Позволяет строить древовидные структуры данных (родословные, деревья каталогов).
* Пример:
class Person:
def __init__(self, name, mother=None, father=None):
self.name = name
self.mother = mother # Ссылка на такой же класс Person
self.father = father # Ссылка на такой же класс Person
p1 = Person("Отец")
p2 = Person("Мать")
p3 = Person("Сын", mother=p2, father=p1)
# p3.father.name -> "Отец"
2. Абстрактные базовые классы.
* Ответ: Классы, которые не предназначены для создания экземпляров. Они служат шаблонами (контрактами) для классов-наследников, обязывая их реализовать определенные методы.
* Реализация: Используется библиотека abc (Abstract Base Classes) и декоратор @abstractmethod.
3. Метаклассы.
* Ответ: Это «классы классов». Класс является экземпляром метакласса. Метаклассы позволяют управлять процессом создания самих классов (например, автоматически регистрировать их в списке).
* Код из Лекции 13 (автоматическая регистрация экземпляров):
# Метакласс
class metalist(type):
def __init__(cls, *args):
cls.L = [] # При создании класса создаем у него список L
# Класс, использующий метакласс
class Person(metaclass=metalist):
def __init__(self, name):
self.name = name
# self.__class__.L — это доступ к списку, созданному метаклассом
self.__class__.L.append(self)
p1 = Person("Ivan")
p2 = Person("Maria")
# Список Person.L заполнился автоматически
print(Person.L) # Выведет список объектов p1 и p2
4. Интроспекция.
* Ответ: Способность программы исследовать саму себя во время выполнения: узнавать тип объекта, его атрибуты, имя класса и т.д.
* Инструменты: type(), id(), dir(), getattr(), словари __dict__, globals(), locals().
5. Итераторы.
* Ответ: Объекты, предназначенные для перебора элементов коллекции.
* Протокол итератора:
1. __iter__(): возвращает объект итератора (обычно self).
2. __next__(): возвращает следующий элемент или вызывает StopIteration.
* Код из Лекции 10 (Бесконечное кольцо):
class Ring(list):
def __iter__(self):
self.ind = 0 # Сбрасываем счетчик при начале перебора
return self
def __next__(self):
# Реализуем логику: перебор через один элемент или бесконечно по кругу
res = self[self.ind % len(self)] # Берем элемент по модулю
self.ind += 1
return res
R = Ring([1, 2, 3])
iterator = iter(R)
print(next(iterator)) # 1
print(next(iterator)) # 2
# ...и так далее бесконечно
6. Сопрограммы (Корутины).
* Ответ: Функции, которые могут приостанавливать выполнение и не только отдавать (yield), но и принимать данные извне в процессе работы. Основа асинхронности.
* Код из Лекции 11 (Вычисление среднего арифметического на лету):
def avg():
s = 0
c = 0
r = None
while True:
# yield справа: программа ждет, пока ей пришлют число в 'a'
# и отдает текущий результат 'r'
a = yield r
s += a
c += 1
r = s / c
A = avg()
next(A) # "Прогреваем" сопрограмму (доходим до первого yield)
print(A.send(10)) # Шлем 10 -> ср.знач 10.0
print(A.send(20)) # Шлем 20 -> ср.знач 15.0 ( (10+20)/2 )
7. Хэш-функция.
* Ответ: Функция, преобразующая объект в число. Необходима для того, чтобы объект мог быть ключом в словаре (dict) или элементом множества (set).
* Проблема: По умолчанию списки (list) не хешируемы. Чтобы использовать список как ключ, нужно написать класс-обертку.
* Код из Лекции 12 (Список как ключ словаря):
class hlist(list):
# Хэш-функция: превращаем список в уникальное число
# (как перевод из двоичной системы, если список из 0 и 1)
def __hash__(self):
s = 0
for i, val in enumerate(self):
s += val * (2**i)
return s
# Обязательно нужен метод сравнения
def __eq__(self, other):
return list(self) == list(other)
# Теперь можно использовать как ключ:
D = { hlist([1, 0, 1]): "Key A", hlist([0, 1]): "Key B" }
print(D[hlist([1, 0, 1])]) # Выведет "Key A"
8. Композиция функций.
* Ответ: Применение одной функции к результату другой: $f(g(x))$.
* В лекциях: Реализовано через перегрузку оператора умножения __mul__ в классе-декораторе, что позволяет писать (fact * fib)(x).
9. Отображения, сохраняющие структуру (Функторы).
* Ответ: Применение функции к элементам внутри контейнера без изменения структуры контейнера (например, map для списков).
10. Идея программирования как композиции отображений.
* Ответ: Подход, где программа строится как цепочка преобразований данных, проходящих через функции («стрелочки»), сохраняющие структуру данных.
11. Ленивые вычисления: формулы, индексы, атрибуты.
* Ответ (Лекция 13): Стратегия, при которой вычисления откладываются до момента, когда их результат реально понадобится.
* Ленивые формулы: Классы var и expr. Мы пишем c = a + b, но сложение происходит не сразу. Сложение происходит только при вызове c() или print(c). Строится "дерево выражений".
* Ленивые индексы: В A[x > 5] выражение x > 5 не вычисляется сразу в True/False. Оно передается как функция/выражение внутри индексатора __getitem__, и там применяется к каждому элементу.
* Ленивые атрибуты: Использование @property. Значение атрибута вычисляется в момент обращения к нему (через точку), а не хранится в памяти постоянно.